package dbUtils

import (
	"context"
	sql2 "database/sql"
	"fmt"
	"gitee.com/lv_baobao/gcore"
	"gitee.com/lv_baobao/gcore/helper"
	"gitee.com/lv_baobao/gcore/logHelper"
	"gitee.com/lv_baobao/gcore/orm"
	"github.com/go-xorm/xorm"
	"math"
	"os"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"
	"xorm.io/core"
)

type BaseDao2 struct {
	Prefix   string
	Dbkey    string
	whereMap map[string]interface{}
	orderStr strings.Builder
	takeSize int32

	engine         *xorm.Engine
	useTransaction bool
	DisableSyncDb  bool
	lock           sync.Mutex
}

func init() {
	enableSyncDb = gcore.NewAppSettingsHelper().GetAppConfig().EnableSyncDb
	if enableSyncDb {
		os.MkdirAll("dblog/logs", 0766)
		os.MkdirAll("dblog/senderLogs", 0766)
		helper.NewScheduleHelper().Start("sync db task", time.Second*1, func() {
			NewDbSyncClient().StartSyncDbData()
		})
	}
}

func NewBaseDao2() *BaseDao2 {
	dao :=
		&BaseDao2{
			Dbkey: "writedb",
		}
	return dao
}

func (dao *BaseDao2) EnableSyncDb() bool {
	if enableSyncDb && !dao.DisableSyncDb {
		return true
	}
	return false
}

func (dao *BaseDao2) SelectAll(beans interface{}, ctx context.Context) error {
	defer dao.Close()
	var err error
	err = dao.getDbSession(ctx).Find(beans)
	if err != nil {
		logHelper.Error(err)
	}
	return err
}

func (dao *BaseDao2) GetById(id interface{}, bean interface{}, tableName string, ctx context.Context) (bool, error) {
	defer dao.Close()
	var err error
	var bGet bool
	bGet, err = dao.getDbSession(ctx).ID(id).Table(tableName).Get(bean)
	if err != nil {
		logHelper.Error(err)
	}
	return bGet, err
}

func (dao *BaseDao2) DeleteById(id int64, tableName string, ctx context.Context) error {
	defer dao.Close()
	var sql = fmt.Sprintf("delete from %s where id=? ", tableName)
	var err error
	_, err = dao.getDbSession(ctx).Exec(sql, id)
	if err != nil {
		logHelper.Error(err)
	} else {
		if dao.EnableSyncDb() {
			dbConfig := NewDbHelper().GetDbConfig(dao.Dbkey)
			NewDbLogHelper().WriteDeletedLog(tableName, []string{"and id=?"}, []interface{}{id}, dbConfig.DataBase, dao.Dbkey)
		}
	}
	return err
}

func (dao *BaseDao2) DeleteAll(tableName string, ctx context.Context) error {
	defer dao.Close()
	var sql = fmt.Sprintf("delete from %s ", tableName)
	var err error
	_, err = dao.getDbSession(ctx).Exec(sql)
	if err != nil {
		logHelper.Error(err)
	} else {
		if dao.EnableSyncDb() {
			dbConfig := NewDbHelper().GetDbConfig(dao.Dbkey)
			NewDbLogHelper().WriteDeletedLog(tableName, []string{}, []interface{}{}, dbConfig.DataBase, dao.Dbkey)
		}
	}
	return err
}

func (dao *BaseDao2) insertOne(i interface{}, tableName string, ctx context.Context) (int64, error) {
	dialect := NewDbHelper().GetDialect(dao.Dbkey)
	defer dao.Close()
	if dialect == "mysql" {
		return dao.getDbSession(ctx).Table(tableName).Insert(i)
	}
	schema := orm.NewSchema(i)
	sqlVars := schema.GetInsertRecordValues(nil)
	var sql = strings.Builder{}
	var insertFieldNames = make([]string, 0)
	for _, field := range schema.Fields {
		if !field.AutoIncr {
			insertFieldNames = append(insertFieldNames, field.Name)
		}
	}
	table := tableName
	if table == "" {
		table = schema.Name
	}
	sql.WriteString(fmt.Sprintf("insert into %s(%s) VALUES", table, strings.Join(insertFieldNames, ",")))

	for idx, _ := range insertFieldNames {
		if idx == 0 {
			sql.WriteString("(?")
			if len(insertFieldNames) == 1 {
				sql.WriteString(")")
			}
		} else if idx == (len(insertFieldNames) - 1) {
			sql.WriteString(",?)")
		} else {
			sql.WriteString(",?")
		}
	}
	if schema.AutoPkField != nil {
		if dialect == "mysql" {
			//sql.WriteString(";select last_insert_id() id;")
		} else if dialect == "mssql" {
			sql.WriteString(";select SCOPE_IDENTITY() id")
		}
	}
	//logHelper.Info(sql.String(), sqlVars)
	sqlOrArgs := make([]interface{}, 0)
	sqlOrArgs = append(sqlOrArgs, sql.String())
	if sqlVars != nil && len(sqlVars) > 0 {
		sqlOrArgs = append(sqlOrArgs, sqlVars...)
	}
	if schema.AutoPkField != nil {
		var err error
		var query []map[string]string
		query, err = dao.getDbSession(ctx).QueryString(sqlOrArgs...)
		if err != nil {
			return 0, err
		}
		var id int
		id, _ = strconv.Atoi(query[0]["id"])
		idValue := reflect.Indirect(reflect.ValueOf(i)).FieldByName(schema.AutoPkField.Name)
		pkFieldTypeStr := schema.AutoPkField.Type.String()
		if idValue.CanSet() {
			if pkFieldTypeStr == "int64" {
				idValue.Set(reflect.ValueOf(int64(id)))
			} else {
				idValue.Set(reflect.ValueOf(id))
			}
		}
	} else {
		var err error
		var exec sql2.Result
		exec, err = dao.getDbSession(ctx).Exec(sqlOrArgs...)
		if err != nil {
			return 0, err
		}
		return exec.RowsAffected()
	}
	return 1, nil
}

func (dao *BaseDao2) Insert(bean interface{}, tableName string, ctx context.Context) (int64, error) {
	defer dao.Close()
	effectRows, err := dao.insertOne(bean, tableName, ctx)
	if err != nil {
		logHelper.Error(err)
		return 0, err
	} else {
		if dao.EnableSyncDb() {
			dbConfig := NewDbHelper().GetDbConfig(dao.Dbkey)
			NewDbLogHelper().WriteInsertedLog(bean, tableName, dbConfig.DataBase, dao.Dbkey)
		}
	}
	return effectRows, err
}

func (dao *BaseDao2) InsertByTableName(bean interface{}, tableName string, ctx context.Context) (int64, error) {
	defer dao.Close()
	one, err := dao.insertOne(bean, tableName, ctx)
	if err == nil {
		if dao.EnableSyncDb() {
			dbConfig := NewDbHelper().GetDbConfig(dao.Dbkey)
			NewDbLogHelper().WriteInsertedLog(bean, tableName, dbConfig.DataBase, dao.Dbkey)
		}
	}
	return one, err
}

func (dao *BaseDao2) BatchInsert(beans interface{}, tableName string, ctx context.Context) (int64, error) {
	defer dao.Close()
	var err error
	var insert int64
	insert, err = dao.getDbSession(ctx).Table(tableName).Insert(beans)
	return insert, err
}

func (dao *BaseDao2) Update(i interface{}, tableName string, ctx context.Context) (int64, error) {
	var sql = strings.Builder{}
	schema := orm.NewSchema(i)
	table := tableName
	if table == "" {
		table = schema.Name
	}
	sql.WriteString(fmt.Sprintf("update %s set ", table))
	var pkField = schema.PkField
	var isFirstSet = true
	for _, field := range schema.Fields {
		if field.IsPk {
			continue
		}
		if isFirstSet {
			sql.WriteString(fmt.Sprintf("%s=?", field.Name))
			isFirstSet = false
		} else {
			sql.WriteString(fmt.Sprintf(",%s=?", field.Name))
		}
	}
	sql.WriteString(fmt.Sprintf(" where %s=?", pkField.Name))
	sqlVars := schema.GetUpdateRecordValues(i)
	defer dao.Close()
	sqlOrArgs := make([]interface{}, 0)
	sqlOrArgs = append(sqlOrArgs, sql.String())
	for _, sqlVar := range sqlVars {
		sqlOrArgs = append(sqlOrArgs, sqlVar)
	}
	pkFieldValue := schema.GetFieldValue(schema.Value, *pkField)
	sqlOrArgs = append(sqlOrArgs, pkFieldValue)

	var err error
	var exec sql2.Result
	exec, err = dao.getDbSession(ctx).Exec(sqlOrArgs...)
	if err != nil {
		return 0, err
	}
	affected, err := exec.RowsAffected()
	if affected > 0 {
		if dao.EnableSyncDb() {
			dbConfig := NewDbHelper().GetDbConfig(dao.Dbkey)
			NewDbLogHelper().WriteUpdatedLogByEntity(i, tableName, dbConfig.DataBase, dao.Dbkey)
		}
	}
	return affected, err
}

func (dao *BaseDao2) Queryable() IQueryable2 {
	dao.whereMap = make(map[string]interface{})
	dao.orderStr = strings.Builder{}
	var db = dao.GetDbHelper()
	var qr IQueryable2
	if db.DriverName() == "mysql" {
		//queryable := MySqlQueryable{
		//	WhereMap:  map[string]interface{}{},
		//	OrderStr:  strings.Builder{},
		//	TableName: dao.TableName,
		//	Dbkey:     dao.Dbkey,
		//	dao:       dao,
		//}
		//queryable.IQueryable = &queryable
		//qr = &queryable
	} else {
		queryable := MsSqlQueryable2{
			WhereMap: map[string]interface{}{},
			OrderStr: strings.Builder{},
			//TableName: dao.TableName,
			Dbkey: dao.Dbkey,
			//engine: dao.engine,
			//Session: dao.Session,
			//useTransaction: dao.useTransaction,
			dao: dao,
		}
		queryable.IQueryable2 = &queryable
		qr = &queryable
	}
	return qr
}

func (dao *BaseDao2) GetLikeStr(string2 string) string {
	var str = "%" + string2 + "%"
	return str
}

func (dao *BaseDao2) GetDbHelper() *xorm.Engine {
	if dao.engine != nil {
		return dao.engine
	}
	dao.lock.Lock()
	defer dao.lock.Unlock()
	if dao.engine != nil {
		return dao.engine
	}
	var engine *xorm.Engine
	if dao.Dbkey == "" {
		engine = NewDbHelper().GetWriteDb()
	} else {
		engine = NewDbHelper().GetDbByKey(dao.Dbkey)
	}
	if dao.Prefix != "" {
		tbMapper := core.NewPrefixMapper(core.SameMapper{}, dao.Prefix)
		engine.SetTableMapper(tbMapper)
	}

	dao.engine = engine
	return engine
}

func (dao *BaseDao2) getDbSession(ctx context.Context) *xorm.Session {
	if ctx == nil {
		return dao.GetDbHelper().NewSession()
	}
	session, ok := ctx.Value("session").(*xorm.Session)
	if ok {
		return session
	}
	return dao.GetDbHelper().NewSession()
}

func (dao *BaseDao2) ExecuteSql(sql string, args []interface{}, ctx context.Context) error {
	defer dao.Close()
	sqlOrArgs := make([]interface{}, 0)
	sqlOrArgs = append(sqlOrArgs, sql)
	if args != nil && len(args) > 0 {
		sqlOrArgs = append(sqlOrArgs, args...)
	}
	var err error
	_, err = dao.getDbSession(ctx).Exec(sqlOrArgs...)
	if err != nil {
		logHelper.Error(err)
	} else {
		if dao.EnableSyncDb() {
			dbConfig := NewDbHelper().GetDbConfig(dao.Dbkey)
			NewDbLogHelper().WriteExecuteSqlLog(sql, args, dbConfig.DataBase, dao.Dbkey)
		}
	}
	return err
}

//func (dao *BaseDao2) ContinueTrans(session *xorm.Session) {
//
//	dao.useTransaction = true
//	dao.Session = session
//}

func (dao *BaseDao2) BeginTrans(ctx context.Context) (error, context.Context) {
	println(ctx)
	dao.useTransaction = true
	session := dao.GetDbHelper().NewSession()
	err := session.Begin()
	value := context.WithValue(ctx, "session", session)
	return err, value
}

func (dao *BaseDao2) Commit(ctx context.Context) error {
	session := ctx.Value("session").(*xorm.Session)
	defer session.Close()
	err := session.Commit()
	if err != nil {
		logHelper.Error(err)
	}
	return err
}

func (dao *BaseDao2) Rollback(ctx context.Context) error {
	session := ctx.Value("session").(*xorm.Session)
	defer session.Close()
	err := session.Rollback()
	if err != nil {
		logHelper.Error(err)
	}
	return err
}

func (dao *BaseDao2) Close() error {
	//非事务模式下不用关闭
	//事务模式下外面业务代码显示关闭session,engine不用关闭
	return nil
}

func (dao *BaseDao2) SearchByPage(sql string, request searchRequest, listPtr interface{}, ctx context.Context) *helper.QueryResult {
	sql = strings.ReplaceAll(sql, "#STRWHERE#", request.QueryCondition.String())
	sql = strings.ReplaceAll(sql, "#PageSize#", strconv.Itoa(request.PageSize))
	sql = strings.ReplaceAll(sql, "#Start#", strconv.Itoa(request.PageSize*(request.PageIndex-1)))
	sqlArr := strings.Split(sql, ";")
	if len(sqlArr) < 2 {
		logHelper.Error("分页查询语句有错误，必须包含countsql,listsql")
		return nil
	}
	defer dao.Close()
	var res = new(helper.QueryResult)
	res.PageSize = request.PageSize
	res.PageIndex = request.PageIndex

	var countSqlOrArgs = make([]interface{}, 0)
	var listArgs = make([]interface{}, 0)
	countSqlOrArgs = append(countSqlOrArgs, sqlArr[0])

	var keys = make([]string, 0)
	for k := range request.Args {
		keys = append(keys, k)
		elems := request.Args[k]
		if ids, ok := elems.([]interface{}); ok {
			for _, id := range ids {
				countSqlOrArgs = append(countSqlOrArgs, id)
				listArgs = append(listArgs, id)
			}
		} else {
			countSqlOrArgs = append(countSqlOrArgs, elems)
			listArgs = append(listArgs, elems)
		}
	}
	var query []map[string][]byte
	var err error
	query, err = dao.getDbSession(ctx).Query(countSqlOrArgs...)
	if err != nil {
		logHelper.Error(err)
		return res
	}
	m := query[0]
	var k = ""
	for s := range m {
		k = s //获取key
		break
	}
	var rowsCount, _ = strconv.ParseInt(string(query[0][k]), 10, 32)
	res.TotalRecords = rowsCount
	ceil := math.Ceil(float64(rowsCount) / float64(request.PageSize))
	res.TotalPage = int64(ceil)
	res.PageCount = res.TotalPage
	err = dao.getDbSession(ctx).SQL(sqlArr[1], listArgs...).Find(listPtr)
	if err != nil {
		logHelper.Error(err)
	}
	res.ResultList = listPtr
	return res
}
